﻿#if NETFX_CORE || WINDOWS_PHONE
using Socket =  Windows.Networking.Sockets.StreamSocket;

#else

using Socket = System.Net.Sockets.Socket;


#endif

using System;
using System.IO;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using XmlSockets.Internal;
using XmlSockets.Internal.AbSystem;




namespace XmlSockets
{
    


    /// <summary>
    /// XmlSocket
    /// </summary>
    public sealed class XmlSocket:IDisposable
    {


        /// <summary>
        /// Endpoint that has been assigned to this socket
        /// </summary>
        public IEndPoint LocalEndPoint
        {
            get
            {
                return this.Socket.LocalEndPoint;
            }
        }

        /// <summary>
        /// Endpoint remote destination
        /// </summary>
        public IEndPoint RemorteEndPoint
        {
            get
            {
                return this.Socket.RemorteEndPoint;
            }
        }
        
        
        /// <summary>
        ///
        /// </summary>
        internal XmlMessageReader Reader { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        internal XmlMessageWriter Writer { get; private set; }


        /// <summary>
        /// 
        /// </summary>
        public bool Connected { get; internal set; }




        /// <summary>
        /// 
        /// </summary>
        private XmlSocket(XmlSocketCreateInfo careateInfo) : this(InternalSocket.Create(), careateInfo) { }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="socket"></param>
        private XmlSocket(IInternalSocket socket) : this(socket,new XmlSocketCreateInfo()) { }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="socket"></param>
        ///<param name="createInfo"></param>
        internal XmlSocket(IInternalSocket socket, XmlSocketCreateInfo createInfo)
        {

            Socket = socket;

            Writer = new XmlMessageWriter(createInfo.SendBuffer);

            Reader = new XmlMessageReader(createInfo.RecieveBuffer, createInfo.RecieveTmpBuffer, createInfo.MaxMessageSize);

            Connected = false;

        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="hostName"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        public async Task ConnectAsync(string hostName, int port)
        {
            if (this.Connected) throw new InvalidOperationException("Already connected");
            if (hostName == null) throw new ArgumentNullException();
            else if (port < 0 || port > 65535) throw new ArgumentException();
            await Socket.ConnectAsync(hostName, port).ConfigureAwait(false);
            this.Connected = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.Xml.XmlException"></exception>
        public async Task SendAsync(string message)
        {
            if (message == null) throw new ArgumentNullException();
            await SendAsync(XElement.Parse(message)).ConfigureAwait(false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        public async Task SendAsync(XElement message)
        {
            if (message == null) throw new ArgumentNullException();
            await SendAsync(writer =>
            {
                message.WriteTo(writer);
            }).ConfigureAwait(false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sendHandler"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        public async Task SendAsync(XmlSocketSendHandler sendHandler)
        {
            
            if (sendHandler == null ) throw new ArgumentNullException();

            await MustConnectedFunc(async () =>
            {
                IXmlSocketSendContent content;
                lock (Writer)
                {
                    sendHandler(Writer.InternalWriter);

                    Writer.InternalWriter.Flush();
                    content = this.Writer.CreateContent(XmlSocketSendContent.Create);
                }
                await Socket.SendAsync(content).ConfigureAwait(false);


            }).ConfigureAwait(false);

        }



        /// <summary>
        /// 
        /// </summary>
        /// <param name="receiveHandler"></param>
        /// <returns></returns>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        public async Task ReceiveAsync(XmlSocketReceiveHandler receiveHandler)
        {
            
            if (receiveHandler == null  ) throw new ArgumentNullException();

            await MustConnectedFunc(async () =>
            {
                IXmlSocketReceiveContent content = null;
                try
                {
                    content = await Socket.ReceiveAsync(Reader.ReceiveTmpBufferSize).ConfigureAwait(false);
                }
                catch(Exception e)
                {
                    throw new IOException(e.Message, e);
                }

                if (content.ReceiveLength <= 0) return;

                lock (Reader)
                {
                    
                    this.Reader.ReadContent(content);
                    try
                    {
                        Reader.SetAvaileRead();
                        XmlMessageEnumerator enumerator = new XmlMessageEnumerator(Reader);
                        receiveHandler(enumerator);
                    }
                    finally
                    {
                        Reader.ResetAvaileRead();
                    }
                }
            }).ConfigureAwait(false);
        }


        /// <summary>
        /// 
        /// </summary>
        public void Close()
        {
            this.close();
        }
 

       

        /// <summary>
        /// 
        /// </summary>
        internal IInternalSocket Socket { get; private set; }




        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {

            close();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public static XmlSocket Create(Socket socket)
        {
            return Create(socket, new XmlSocketCreateInfo());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="createInfo"></param>
        /// <returns></returns>
        public static XmlSocket Create(Socket socket, XmlSocketCreateInfo createInfo)
        {
            var xmlSocket = new XmlSocket(InternalSocket.Create(socket), createInfo);
            xmlSocket.Connected =true;
            return xmlSocket;
        }





        #region Private

       
        private async Task MustConnectedFunc(Func<Task> action)
        {
            if (!this.Connected) throw new InvalidOperationException("Socket is not connected");
            try
            {
                await action().ConfigureAwait(false);

            }
                
            catch (IOException e)
            {

                if (this.Connected)
                {
                    this.Close();

                }
                throw new IOException(e.Message, e);
            }
        }


        private void close()
        {
            this.Connected = false;
            this.Socket.Dispose();
            lock (Writer) lock (Reader)
                {
                    this.Writer.Clear();
                    this.Reader.Clear();
                }
        }
        
        #endregion
    }
}
